import path from 'path'
import fs from 'fs-extra'
import { assignObject } from 'flex-tools'
import shelljs  from "shelljs"


export class InvalidProjectPathError extends Error{}

export function getPackageTool():string{
    const projectFolder =  getProjectRootPath(process.cwd())
    if(projectFolder==null){
        throw new Error("未发现package.json,当前工程不是NPM项目")
    }
    if(fs.existsSync(path.join(projectFolder,"pnpm-lock.yaml"))){        
        return 'pnpm'
    }else if(fs.existsSync(path.join(projectFolder,"yarn.lock"))){
        return 'yarn'
    }else{
        return 'npm'
    } 
}   

/**
 * 返回当前项目根文件夹
 * 如果当前不是一个有效的项目，则触发InvalidProjectPathError
 * @param entryFolder 
 * @param exclueCurrent 
 * @returns 
 */
export function getProjectRootPath(entryFolder:string="./",exclueCurrent:boolean=false):string {
    if(!path.isAbsolute(entryFolder)){
        entryFolder = path.join(process.cwd(),entryFolder)
    }
    try{ 
        const pkgFile =exclueCurrent ? 
                        path.join(entryFolder, "..", "package.json")
                        : path.join(entryFolder, "package.json")
        if(fs.existsSync(pkgFile)){ 
            return path.dirname(pkgFile)
        }
        const parent = path.dirname(entryFolder)
        if(parent===entryFolder){
            throw new InvalidProjectPathError()
        }
        return getProjectRootPath(parent,false)
    }catch(e){
        throw new InvalidProjectPathError()
    }
}


/**
 * 异步执行脚本并返回输出结果
 * @param {*} script 
 * @param {*} options 
 * @returns 
 */
export async function asyncExecShellScript(script:string,options?:{silent?:boolean}){
    const { silent} = assignObject({silent:false},options)
    return new Promise((resolve,reject)=>{
        shelljs.exec(script,{silent,...options,async:true},(code:number,stdout:string)=>{
            if(code>0){
                reject(new Error(`执行<${script}>失败: ${stdout.trim()}`))
            }else{
                resolve(stdout.trim())
            }
        })   
    }) 
} 



export interface installPackageOptions{
    silent?: boolean                                // 执行安装时静默输出
    type?: 'prod' | 'dev' | 'peer' | 'optional'     // 安装开发依赖
    global?: boolean                                // 安装为全局依赖
    upgrade?: boolean                               // 当依赖已经安装时是否进行升级 
}
/**
 * 在当前项目下安装指定的包
 * @param packageName 
 * @param param1 
 */
export async function installPackage(packageName:string,options?:installPackageOptions){
    const {silent,type,global:isGlobal,upgrade} = assignObject({
        silent:true,
        type:'prod',
        upgrade:true,               // 当包已经安装时,是否升级到最新版本
    },options)
    const packageTool = getPackageTool()
    let args = []
    
    const isInstalled = await packageIsInstalled(packageName)
    if(isInstalled && upgrade){
        const { version:oldVersion } = getPackageInfo(packageName)
        if(packageTool=='pnpm'){           
            await asyncExecShellScript(`pnpm upgrade  --latest ${packageName}`,{silent}) 
        }else if(packageTool=='yarn'){
            await asyncExecShellScript(`yarn upgrade --latest ${packageName}`,{silent})        
        }else{
            await asyncExecShellScript(`npm upgrade ${packageName}`,{silent})        
        }  
        const { version:newVersion } = getPackageInfo(packageName)
        return oldVersion == newVersion ? "NEWEST" : `${oldVersion}->${newVersion}`
    }else{
        if(packageTool=='pnpm'){
            if(isGlobal) args.push("-g")
            if(isMonorepoProject()) args.push("-w")
            if(type=='dev') args.push("-D")
            if(type=='peer') args.push("-P")
            if(type=='optional') args.push("-O")
            await asyncExecShellScript(`pnpm add ${args.join(" ")} ${packageName}`,{silent}) 
        }else if(packageTool=='yarn'){
            if(isGlobal) args.push("-g")
            if(type=='dev') args.push("-D")
            if(type=='peer') args.push("-P")
            if(type=='optional') args.push("-O")
            await asyncExecShellScript(`yarn ${isGlobal ? 'global ' :''}add  ${args.join(" ")} ${packageName}`,{silent})        
        }else{
            if(isGlobal) args.push("-g")
            if(type=='dev') args.push("-D --save-dev")
            if(type=='peer') args.push("-P")
            if(type=='optional') args.push("-O")
            await asyncExecShellScript(`npm install  ${args.join(" ")} ${packageName}`,{silent})        
        }  
    }    
} 
 
/**
 * 判断当前是否是Typescript工程
 * 
 * 
 */
export function isTypeScriptProject():boolean{
    let projectFolder = getProjectRootPath(process.cwd(),false)
    if(projectFolder){
       return fs.existsSync(path.join(projectFolder,"tsconfig.json"))
             || fs.existsSync(path.join(projectFolder,"src","tsconfig.json"))
    }else{
        return false
    }
}


export function getPackageInfo(packageName:string){    
    const packagePath = getProjectRootPath(require.resolve(packageName))
    if(packagePath){
        return fs.readJsonSync(path.join(packagePath,"package.json"))    
    }else{
        throw new InvalidProjectPathError()
    }    
}

export function getCurrentPackageInfo(){    
    const packagePath = getProjectRootPath()
    if(packagePath){
        return fs.readJsonSync(path.join(packagePath,"package.json"))    
    }else{
        throw new InvalidProjectPathError()
    }    
}

export async function runNpmScript(script:string,options?:{silent?:boolean}){
    let tool = getPackageTool()
    return await asyncExecShellScript(`${tool} run ${script}`,options)
}

export async function execProjectShellCommand(script:string,options?:{silent?:boolean}){
    let tool = getPackageTool()
    if(tool='yarn'){
        return await asyncExecShellScript(`${tool} ${script}`,options)
    }else{
        return await asyncExecShellScript(`${tool} exec ${script}`,options)
    }    
}


/**
 * 查询指定的包是否安装，如果安装则返回版本号
 * @param packageName
 */
export async function packageIsInstalled(packageName:string,checkGlobal:boolean=false):Promise<boolean>{
    const packageRoot = getProjectRootPath()
    if(!packageRoot) throw new InvalidProjectPathError()
    let installed:boolean = false
    //
    try{
        //require(path.join(packageRoot,`node_modules/${packageName}/package.json`))
        require.resolve(packageName)
        installed = true
    }catch{
        return false
    }
    if(checkGlobal){
        try{
            const npmRootPath = await asyncExecShellScript("npm root") as  string
            if(npmRootPath){
                require(path.join(npmRootPath,`node_modules/${packageName}/package.json`))
                installed = true
            }            
        }catch(e){}        
    }
    return installed
}


/**
 * 当前工程是否是工作区
 */
export function isMonorepoProject(){
    let projectFolder = getProjectRootPath(process.cwd(),false)
    if(projectFolder){
       return fs.existsSync(path.join(projectFolder,"pnpm-workspace.yaml")) 
    }else{
        return false
    }
}

